package server;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicBoolean;
import communication.Connection;
import constans.SizeConstans;
import capsules.CapsuleSpecification;
import capsules.CapsuleState;
import client.Identifier;
import client.Information;
import client.Operation;
import communication.ConnectionException;
import java.util.logging.Level;
import java.util.logging.Logger;


class ThreadReplication extends Thread{

	private Connection connection;	
	private Authenticator authenticator;
	private Replicator replicator;
	private Updater updater;		
	private Verifier verifier;	
	private File folderCapsule;
    private AtomicBoolean isStable;
    private Logger logger;    
	private Information information;
	private int numberCapsules;
	private boolean isLogin;
	private boolean isCorrect;
    private int maxReplication;
    private long usedSpace;
	



	public ThreadReplication(Connection connection, Authenticator authenticator, Replicator replicator,
            Verifier verifier, Updater updater, File folderCapsule , AtomicBoolean isStable,Logger logger,
            int maxReplication){
		this.connection = connection;
		this.authenticator = authenticator;
		this.replicator = replicator;		
		this.verifier = verifier;
		this.updater = updater;	
		this.folderCapsule = folderCapsule;
        this.isStable = isStable;
        this.logger = logger;
		this.isLogin = false;
		this.isCorrect = true;
		this.numberCapsules = 0;
        this.maxReplication = maxReplication;
        this.usedSpace = 0;
	}


    @Override
	public void run(){	

		try {

			Operation operation = (Operation) connection.receiveObject();			

			if(operation.equals(Operation.LOGIN)){          
				doLogin();
			} else{
				isCorrect = false;
			}

			if(isCorrect && isLogin){

				operation = (Operation) connection.receiveObject();

				if(operation.equals(Operation.VERIFY_STATE_SERVER)){
					connection.send(ServerState.REPLICATION);
                    logger.log(Level.INFO,"Operation.VERIFY_STATE_SERVER("+information.getEmail()+")");
                }
				else{
					isCorrect = false;
                }
			}


            while(isCorrect && isLogin){

				operation = (Operation) connection.receiveObject();

                switch(operation){
                    case SEND_OFFERED_SPACE:
                        logger.log(Level.INFO,"Operation.SEND_OFFERED_SPACE("+information.getEmail()+")");
                        doOfferedSpace();
                        break;
                    case ASK_CAPSULE_SPECIFICATION:
                        logger.log(Level.INFO,"Operation.ASK_CAPSULE_SPECIFICATION("+information.getEmail()+")");
                        doAskCapsuleSpecification();
                        break;
                    case VERIFY_STATE_CAPSULE:
                        logger.log(Level.INFO,"Operation.VERIFY_STATE_CAPSULE("+information.getEmail()+")");
                        doVerifyCapsuleState();
                        break;
                    case DOWNLOAD_CAPSULE:
                        logger.log(Level.INFO,"Operation.DOWNLOAD_CAPSULE("+information.getEmail()+")");
                        doDownloadCapsule();
                        break;
                    case LOGOUT:
                        logger.log(Level.INFO,"Operation.LOGOUT("+information.getEmail()+")");
                        doLogout();
                        isLogin= false;
                        break;
                    default:
                        isCorrect= false;
                        break;
                }
             }
        } catch (ClassCastException e) {
             logger.log(Level.WARNING,"ClassCastException: "+e.getMessage());
        } catch (ConnectionException e) {
             logger.log(Level.WARNING,"ConnectionException: "+e.getMessage());
        } catch (FileNotFoundException e) {
             logger.log(Level.WARNING,"FileNotFoundException: "+e.getMessage());
        } catch (IOException e) {
             logger.log(Level.WARNING,"IOException: "+e.getMessage());
        } catch (SQLException e) {
            isStable.set(false);
            logger.log(Level.SEVERE,"SQLException: "+e.getMessage());
		}
        finally{

                try{
                       if(connection!=null){
                            connection.close();
                            connection = null;
                        }
                 }
                catch (ConnectionException e) {}

                try {
                    if(information!=null){
                       doLogout();
                       updateNumberCapsulesIdentifier();
                       updateUsedSpaceIdentifier();
                    }
                }
                catch (SQLException e) {
                      logger.log(Level.SEVERE,"SQLException: "+e.getMessage());
                }
		}
	}


	private void doDownloadCapsule()
            throws IOException, ConnectionException,
            ClassCastException{

	   String name  = (String) connection.receiveString();
	long offset  =  (long)   connection.receiveLong();

		if(name!= null && !name.isEmpty() && offset >= 0 ){

			RandomAccessFile file = null;
            byte [] buffer = new byte[(int)SizeConstans.KByte ];
            long numRead = 0;		

            try{

				file = new RandomAccessFile( new File(folderCapsule,name),"r");                
				file.seek(offset);                

				while((numRead = file.read(buffer))!=-1)
						connection.send(buffer,0,(int)numRead);
				connection.flush();

                file.close();

			} catch(FileNotFoundException e){
				try {
					if(file!=null)
						file.close();
				} catch (IOException e1) {}
                throw new FileNotFoundException();
			} catch(IOException e){
				try {
					if(file!=null)
						file.close();
				} catch (IOException e1) {}
				throw new IOException();
			} 	catch (ConnectionException e) {
				try {
					if(file!=null)
						file.close();
				} catch (IOException e1) {}
				throw new ConnectionException();
			}
            
		} else{
			isCorrect = false;
		}
	}


    private void doOfferedSpace()
            throws SQLException, ConnectionException,
            ClassCastException {

       long offeredSpace =  (long) connection.receiveLong();

       updater.setOfferedSpace(information.getEmail(), information.getId(), offeredSpace);
    }

        
    private void updateUsedSpaceIdentifier()
            throws SQLException{
       updater.setUsedSpace(information.getEmail(), information.getId(), usedSpace);
    }


	private void doVerifyCapsuleState()
            throws SQLException,
	ConnectionException,ClassCastException{

		CapsuleSpecification capsuleSpecification = (CapsuleSpecification) connection.receiveObject();

		if(capsuleSpecification!=null){

			CapsuleState capsuleState = verifier.getCapsuleState(capsuleSpecification);

   		     if(capsuleState.equals(CapsuleState.CORRECT)){
                numberCapsules++; 
                usedSpace += capsuleSpecification.getLength();
             }

			connection.send(capsuleState);
		}
		else{
			isCorrect = false;
		}			
	}


	private void doAskCapsuleSpecification()
            throws SQLException, ConnectionException{

		CapsuleSpecification capsuleSpecification = null;

        if(maxReplication>0){
            capsuleSpecification = replicator.getCapsuleSpecification();
            maxReplication--;
        }

		connection.send(capsuleSpecification);
	}		

	
	private void doLogout(){
        authenticator.logoutClient(information.getEmail());
	}

    
    private void updateNumberCapsulesIdentifier()
            throws SQLException{
        updater.setNumberCapsules(information.getEmail(), information.getId(), numberCapsules);
	}


	private void doLogin()
            throws ConnectionException, SQLException,
            IOException, ClassCastException{

		Identifier identifier = (Identifier) connection.receiveObject();

		if(identifier!=null && identifier.getSealedInfo()!=null && identifier.getSignedInfo()!=null){

            if( (information = authenticator.loginClient(identifier))!=null){
    			isLogin = true;         
            }
			else{
				isLogin = false;
            }

			connection.send(isLogin);           

		} else{
			isCorrect = false;
		}        
	}


}

